package com.lemon.exam.common.runner;

import com.lemon.exam.common.annotation.Web;
import com.lemon.exam.common.constant.RedisKeyConst;
import com.lemon.exam.common.entity.system.SysMethod;
import com.lemon.exam.common.entity.system.SysWeb;
import com.lemon.exam.common.redis.RedisService;
import com.lemon.exam.common.service.ElasticsearchService;
import com.lemon.exam.common.util.RequestUtil;
import com.lemon.exam.entity.po.LoggingPO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.webflux.api.MultipleOpenApiWebFluxResource;
import org.springdoc.webflux.api.OpenApiWebfluxResource;
import org.springdoc.webflux.ui.SwaggerConfigResource;
import org.springdoc.webflux.ui.SwaggerWelcomeWebFlux;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootVersion;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 项目启动完成后要执行的业务
 *
 * @author Lemon
 * @since 2025/2/26 10:53
 */
@Slf4j
@Component
public class ApplicationStartRunner implements ApplicationRunner {
    @Resource
    private Environment environment;
    @Resource
    private RequestMappingHandlerMapping requestMappingHandlerMapping;
    @Resource
    private RedisService redisService;
    @Resource
    private ElasticsearchService elasticsearchService;
    @Resource
    private ConfigurableApplicationContext configurableApplicationContext;

    // 排除 knife4j 接口
    private static final List<Class<?>> KNIFE4J_INTERFACE_CLASS = List.of(
            MultipleOpenApiWebFluxResource.class,
            SwaggerWelcomeWebFlux.class,
            OpenApiWebfluxResource.class,
            SwaggerConfigResource.class
    );

    @Override
    public void run(ApplicationArguments args) {
        extractSystemInterfaces()
                .flatMap(this::storeInterfacesToRedis)
                .onErrorReturn(false)
                .publishOn(Schedulers.boundedElastic())
                .doOnSuccess(success -> {
                    if (success) {
                        createIndices();
                        printSystemInfo();
                    } else {
                        throw new RuntimeException("未获取到系统接口");
                    }
                })
                .doOnError(e -> {
                    log.error("系统启动异常：{}", e.getMessage());
                    //终止启动
                    SpringApplication.exit(configurableApplicationContext, () -> 1);
                })
                .subscribe();
    }

    /**
     * 提取系统接口
     *
     * @return
     */
    private Mono<List<SysWeb>> extractSystemInterfaces() {
        return Mono.fromCallable(() -> requestMappingHandlerMapping.getHandlerMethods().entrySet().stream()
                .filter(entry -> !KNIFE4J_INTERFACE_CLASS.contains(entry.getValue().getBeanType()))
                .collect(Collectors.groupingBy(entry -> entry.getValue().getBeanType())).values().stream()
                .flatMap(entries -> processControllerMethods(entries).stream())
                .toList()).subscribeOn(Schedulers.boundedElastic()); // 在弹性线程池执行
    }

    /**
     * 处理控制器方法
     *
     * @param entries
     * @return
     */
    private List<SysWeb> processControllerMethods(List<Map.Entry<RequestMappingInfo, HandlerMethod>> entries) {
        if (entries.isEmpty()) return Collections.emptyList();

        HandlerMethod firstMethod = entries.get(0).getValue();
        Class<?> controllerClass = firstMethod.getBeanType();

        Web webAnnotation = controllerClass.getAnnotation(Web.class);
        Tag tagAnnotation = controllerClass.getAnnotation(Tag.class);
        RequestMapping requestMapping = controllerClass.getAnnotation(RequestMapping.class);

        String controllerName = tagAnnotation != null ? tagAnnotation.name() : null;
        String[] basePaths = requestMapping != null ? (requestMapping.value().length > 0 ? requestMapping.value() : requestMapping.path()) : new String[]{""};

        List<SysMethod> methods = entries.stream()
                .flatMap(entry -> processHandlerMethod(entry.getKey(), entry.getValue()))
                .toList();

        return Arrays.stream(basePaths)
                .map(basePath -> createSysWeb(controllerName, basePath, webAnnotation, methods))
                .toList();
    }

    /**
     * 处理方法级别的映射信息
     *
     * @param mappingInfo
     * @param handlerMethod
     * @return
     */
    @SuppressWarnings("ConstantConditions")
    private Stream<SysMethod> processHandlerMethod(RequestMappingInfo mappingInfo, HandlerMethod handlerMethod) {
        Operation operation = handlerMethod.getMethodAnnotation(Operation.class);

        return mappingInfo.getPatternsCondition()
                .getPatterns()
                .stream()
                .map(pattern -> new SysMethod(
                        operation != null ? operation.summary() + (StringUtils.hasText(operation.description()) ? " [" + operation.description() + "]" : "") : null,
                        normalizePath(pattern.getPatternString(), false),
                        handlerMethod.getMethod().getName().toUpperCase(Locale.ENGLISH))
                );
    }

    /**
     * 创建SysWeb对象
     */
    private SysWeb createSysWeb(String name, String basePath, Web web, List<SysMethod> methods) {
        String normalizedBasePath = normalizePath(basePath, false);

        String parentUri = null;
        String parentComponent = null;
        String childUri = null;
        String childComponent = null;

        if (web != null) {
            parentUri = normalizePath(web.parentUri(), true);
            parentComponent = normalizeComponentPath(web.parentComponent());
            childUri = web.childUri().startsWith("/") ? web.childUri().substring(1) : web.childUri();
            childComponent = normalizeComponentPath(web.childComponent());
        }

        return new SysWeb(name, normalizedBasePath, parentUri, parentComponent, childUri, childComponent, methods);
    }

    /**
     * 存储接口信息到Redis
     */
    private Mono<Boolean> storeInterfacesToRedis(List<SysWeb> interfaces) {
        return redisService.string().del(RedisKeyConst.SYSTEM_INTERFACE)
                .then(redisService.set().add(RedisKeyConst.SYSTEM_INTERFACE, interfaces.toArray()))
                .thenReturn(true);
    }

    /**
     * 正常化路径
     *
     * @param path
     * @param allowEmpty 是否允许路径为空
     * @return
     */
    private String normalizePath(String path, boolean allowEmpty) {
        if (!StringUtils.hasText(path)) {
            return allowEmpty ? "" : "/";
        }

        String normalized = path.startsWith("/") ? path : "/" + path;

        // 处理路径变量
        if (normalized.contains("{") && normalized.contains("}")) {
            return normalized.replaceAll("\\{[^}]+}", "**");
        }

        return normalized;
    }

    /**
     * 组件路径
     *
     * @param componentPath
     * @return
     */
    private String normalizeComponentPath(String componentPath) {
        if (!StringUtils.hasText(componentPath)) {
            return null;
        }

        String normalized = componentPath.startsWith("/") ? componentPath : "/" + componentPath;

        return normalized.endsWith(".vue") ? normalized : normalized + ".vue";
    }

    /**
     * 创建 ES 日志索引
     */
    private void createIndices() {
        elasticsearchService.createIndexIfNotExists(LoggingPO.class)
                .doOnSuccess(s -> log.info("创建索引成功"))
                .doOnError(e -> log.error("创建索引失败：{}", e.getMessage()))
                .then()
                .subscribe();
    }

    /**
     * 打印系统信息
     */
    private void printSystemInfo() {
        //应用名称
        String appName = environment.getProperty("spring.application.name", "unknown");
        //ip
        String ip = RequestUtil.getServerIp();
        //端口
        String port = environment.getProperty("server.port");
        //SpringBoot 版本
        String version = SpringBootVersion.getVersion();
        //上下文路径
        String contextPath = environment.getProperty("server.servlet.context-path", "");
        //本地访问地址
        String localUrl = String.format("http://localhost:%s%s", port, contextPath);
        //外网访问地址
        String externalUrl = String.format("http://%s:%s%s", ip, port, contextPath);
        //文档地址
        String docUrl = String.format("%s/doc.html", externalUrl);

        log.info("\n"
                        + "----------------------------------------------------------\n"
                        + "\tApplication is running!\n"
                        + "\tName: \t\t{}\n"
                        + "\tVersion: \tSpring Boot {}\n"
                        + "\tLocal: \t\t{}\n"
                        + "\tExternal: \t{}\n"
                        + "\tDoc: \t\t{}\n"
                        + "----------------------------------------------------------",
                appName, version, localUrl, externalUrl, docUrl);
    }
}
